# 1.导包
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier  # 分类问题
import pandas as pd
from collections import Counter  # 计数器
import joblib  # 模型的保存和读取，为了提高训练效率
import matplotlib.pyplot as plt


def eda():
    # 1.读取数据
    df = pd.read_csv("../day_02/手写数字识别.csv", encoding="UTF-8")

    # 2.数据拆分成特征值和目标值
    x_data = df.iloc[:, 1:]
    y_data = df.iloc[:, 0]

    # 3.任意取到一条特征数据，将特征值转换成28*28的图片
    img_df = x_data.iloc[8].values.reshape(28, 28)  # 获取值之后修改形状

    # 4.展示图片
    plt.imshow(img_df, cmap="gray")
    plt.show()


def num_ml():
    # 1- 准备数据
    nums_df = pd.read_csv("手写数字识别.csv", encoding="UTF-8")
    x = nums_df.iloc[:, 1:]
    y = nums_df.iloc[:, 0]

    print("各类数字的样本数分别是：", Counter(y))

    # 2- 数据基本处理
    # 2.1- 数据集的划分
    """
        stratify：数据的划分按照哪个字段的比例来进行均匀的划分。如果不指定该参数，会导致测试集中的样本集中在某一类上。
            推荐在数据分布不均衡的时候使用
                
            举例说明：1000条样本中是1和2的样本数据，数据比例是60%:40%。接着进行数据集划分为训练集和测试集比例为7:3，
                    如果不指定stratify参数，可能会导致测试集的300条数据中全是1的样本数据
    """
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=921, stratify=y)

    # 3- 特征工程，特征预处理。
    # 这里使用归一化。因为像素的取值范围是[0,255]，因此归一化的计算公式可以简化为x_train/255
    new_x_train = x_train / 255
    # print(new_x_train)

    # 4- 创建算法模型实例对象
    model = KNeighborsClassifier(n_neighbors=3)     # 模型训练最优参数是3

    # # 4.2 创建交叉验证和网格搜索（为了得到更好的超参数K）
    # param_dict = {
    #     "n_neighbors": [i for i in range(3, 20, 2)]
    # }
    # """
    #     参数解释
    #         estimator：初始化的算法模型
    #         param_grid：网格搜索的参数选择范围，类型是dict字典。key是超参数名称，value是超参数值的选择范围
    #         cv：交叉验证对训练集划分为多少份。一般为20以内
    # """
    #
    # # 网格搜索
    # em = GridSearchCV(
    #     estimator=model,
    #     param_grid=param_dict,
    #     cv=5
    # )
    #
    # # # 4.3- 将训练集的数据输入到交叉验证和网格搜索中
    # em.fit(new_x_train, y_train)
    #
    # # # 4.4- 输出交叉验证和网格搜索的最优参数组合
    # print(em.cv_results_)
    # print(em.best_params_)
    # print(em.best_estimator_)
    # print(em.best_score_)
    # print(em.best_index_)

    # 5- 使用训练集 训练模型
    model.fit(new_x_train, y_train)

    # 5.1- 将训练好的模型保存下来，下次直接使用
    joblib.dump(model, "手写数字识别_3.pkl")  # pkl是数据分析中的特有文件格式。pickle

    # 6- 评估和预测
    x_test = x_test / 255   # 对特征值进行标准化
    print("模型评分", model.score(x_test, y_test))


# 调用已经训练好的模型，对未知数据进行预测
def call_estimator():
    # 1- 加载/读取已经训练好的模型
    model = joblib.load("手写数字识别_tz.pkl")

    # 2- 准备未知数据
    """
    为什么不对plt.imread出来的图片数据进行归一化也就是除以255的操作？
    因为plt.imread内部已经自动进行了归一化的操作
    """
    unknown_img_df = plt.imread("demo.png")
    print(unknown_img_df)

    # 需要将ndarray的结构由28*28，变为1*784
    unknown_img_df = unknown_img_df.reshape(1, 784)
    # unknown_img_df = unknown_img_df.reshape(1, -1)

    # 3- 直接预测
    predict_result = model.predict(unknown_img_df)
    print(f"对图片识别后的数字是：{predict_result}")


if __name__ == '__main__':
    # eda()                   # 数据探索且展示图片
    # num_ml()                # 构建预测模型
    call_estimator()        # 直接使用构建好的模型进行预测
